package ui;

import java.awt.*;
import java.util.ArrayList;
import javax.swing.JWindow;

import core.Preferences;

/**
 * This is a fullscreen undecorated window that displays lyrics or text
 * on a monitor.
 * @author john
 *
 */
public class ScreenDisplay extends JWindow {
	
	public static final int LEFT = 0,
								CENTER = 1,
								RIGHT = 2;
	private static final int MARGIN = 8;	// in px, for left and right align
	private static final long serialVersionUID = 1L;
	private Color bgColor = Color.black,
					fgColor = Color.white;
	private Font mainFont, cjkFont;
	private String text = "";
	private int align = CENTER;
	private Image backgroundImg;
	private boolean blank = false;
	
	/**
	 * Set up a screen as the Preferences object describes.
	 * @param prefs	
	 */
	public ScreenDisplay(Preferences prefs) {
		super(prefs.getDefaultMonitorConfig());
		mainFont = prefs.getMainFont();
		cjkFont = prefs.getCJKFont();
		align = prefs.getAlign();
		bgColor = prefs.getBgColor();
		fgColor = prefs.getFgColor();
		
		if (prefs.debugMode)
			setBounds(0, 0, 640, 480);
		else
			setBounds(prefs.getDefaultMonitorConfig().getBounds());
		setVisible(true);
	}
	
	/**
	 * Sets the main text. This will be displayed in the CJK font if
	 * the first letter is a CJK code point.
	 * @param text
	 */
	public void setText(String text) {
		this.text = text;
		blank = false;
		repaint();
	}
	
	/**
	 * Sets the blank state of the screen. If true, the screen will be completely
	 * blanked, whether the text is null or not.
	 * @param blank		true - blanks the screen; false - unblanks the screen
	 */
	public void setBlank(boolean blank) {
		this.blank = blank;
		repaint();
	}
	/**
	 * Returns whether the screen is currently blanked.
	 * @return		current blank state
	 */
	public boolean isBlank() {
		return blank;
	}
	
	/**
	 * Specify either LEFT, CENTER, or RIGHT.
	 * @param align
	 */
	public void setAlign(int align) {
		this.align = align;
	}
	
	/**
	 * Sets the background image.
	 * @param img
	 */
	public void setBackgroundImage(Image img) {
		backgroundImg = img;
		repaint();
	}
	
	
	private Image bImg = null;
	private Graphics bg = null;
	
	@Override
	public void addNotify() {
		super.addNotify();
		// create double buffer image
		bImg = createImage(getWidth(), getHeight());
		if (bImg != null)
			bg = bImg.getGraphics();
	}
	
	@Override
	public void paint(Graphics g) {
		if (bg != null) {
			render();
			g.drawImage(bImg, 0, 0, getWidth(), getHeight(), 0, 0, getWidth(), getHeight(), null);
		}
	}
	
	private void render() {
		Graphics2D bg2d = (Graphics2D)bg;
		bg2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		
		// clear bg
		bg2d.setColor(bgColor);
		bg2d.fillRect(0, 0, getWidth(), getHeight());
		
		if (!blank && text != null) {
			// draw bg img
			if (backgroundImg != null)
				bg2d.drawImage(backgroundImg, 0, 0, getWidth(), getHeight(),
						0, 0, backgroundImg.getWidth(null), backgroundImg.getHeight(null), null);
			
			// draw main text
			bg2d.setColor(fgColor);
			bg2d.setFont(mainFont);
			
			// split text into lines less than total width
			ArrayList<String> lines = new ArrayList<String>();
			ArrayList<Font> fonts = new ArrayList<Font>();
			String[] tmpLines = text.split("\n");
			int texth = 0;
			for (String line : tmpLines) {
				Font f = containsCJKchars(line) ? cjkFont : mainFont;
				
				if (isCommented(line))
					continue;
				
				if (isItalicized(line)) {
					f = f.deriveFont(Font.ITALIC);
					line = line.substring(3);
				}
				FontMetrics fm = bg2d.getFontMetrics(f);
				
				if (fm.stringWidth(line) > getWidth()) {
					ArrayList<String> split = splitLine(line, fm, getWidth());
					lines.addAll(split);
					for(int i=0; i<split.size(); i++)
						fonts.add(f);
					texth += split.size() * fm.getHeight();
				}
				else {
					lines.add(line);
					fonts.add(f);
					texth += fm.getHeight();
				}
			}
			
			FontMetrics fm = bg2d.getFontMetrics((fonts.size()>0) ? fonts.get(0) : mainFont);
			int y = (getHeight() - texth) / 2 + fm.getAscent();
			for (int i=0; i<lines.size(); i++) {
				String line = lines.get(i);
				Font f = fonts.get(i);
				int x = 0;
				if (align == LEFT)
					x = MARGIN;
				else if (align == RIGHT)
					x = getWidth() - fm.stringWidth(line) - MARGIN;
				else 	// default center
					x = (getWidth() - fm.stringWidth(line)) / 2;
				bg2d.setFont(f);
				bg2d.drawString(line, x, y);
				y += fm.getDescent();
				fm = bg2d.getFontMetrics(i<fonts.size()-1 ? fonts.get(i+1) : mainFont);
				y += fm.getAscent();
				//y += fm.getHeight();
			}
			
			// TODO draw bg img, title, copyright text
		}
	}
	
	/**
	 * Splits a string so that it will fit on one screen of given width
	 * when displayed with a font with the given FontMetrics.
	 * @param line		input string
	 * @param fm		FontMetrics of the display font
	 * @param maxW		screen width, in pixels
	 * @return		an ArrayList of strings that each can fit on one line
	 */
	private static ArrayList<String> splitLine(String line, FontMetrics fm, int maxW) {
		ArrayList<String> ret = new ArrayList<String>();
		String nextLine = "";
		
//		System.out.println("splitLine called with: " + line);
		while (fm.stringWidth(line) > maxW && line.length() > 0) {
			// try to split by space, then by letter
			int i = line.lastIndexOf(' ');
			if (i == -1) i = line.length()-1;
				
			nextLine = line.substring(i) + nextLine;
			line = line.substring(0, i);
//			System.out.println(" > split at i=" + i + ", line=" + line + ", nextLine=" + nextLine);
		}
		
		ret.add(line.trim());
		if (fm.stringWidth(nextLine) > maxW)
			ret.addAll(splitLine(nextLine.trim(), fm, maxW));
		else
			ret.add(nextLine.trim());
		
//		System.out.println("splitLine returns: " + ret.toString());
		return ret;
	}
	
	/**
	 * Checks if the FIRST CODE POINT in the string is a CJK character
	 * (if it lies within the range of CJK characters given by
	 * Unicode standard 5.0.0).
	 * @param str	input string
	 * @return		true if first code point is CJK character
	 */
	// data from  http://www.unicode.org/Public/UNIDATA/Blocks.txt
	// ver 5.0.0  dated 2006-02-15
	private static boolean containsCJKchars(String str) {
//		for (int i=0; i<str.length(); i+=1) {
		if (str.length() > 0) {
			int cp = str.codePointAt(0);
			if ((cp >= 0x3400 && cp <= 0x9FFF) ||
					(cp >= 0xF900 && cp <= 0xFAFF) ||
					(cp >= 0x20000 && cp <= 0x2A6DF) ||
					(cp >= 0x2F800 && cp <= 0x2FA1F))
				return true;
		}
		return false;
	}
	
	/**
	 * Returns true if the string is specially marked as italicized
	 * text (if it is prefixed with &lt;i&gt; at the very beginning).
	 * @param str	input string
	 * @return		true if string is marked as italicized
	 */
	private static boolean isItalicized(String str) {
		return str.startsWith("<i>");
	}
	
	/**
	 * Returns true if the string is marked as a comment (if it is 
	 * prefixed with a period at the very beginning).
	 * @param str	input string
	 * @return		true if string is marked as italicized
	 */
	private static boolean isCommented(String str) {
		return str.startsWith(".");
	}
	
//	public static void main(String[] args) {
		//new ScreenDisplay(0).setText("i'm one line\nof a multi-lined\nbody of text");
		//new ScreenDisplay(0).setText("i'm likely too long to be displayed on one line of text " +
		//		"so the program will split me up into multiple lines like a good program should");
		//new ScreenDisplay(0).setText("i'mlikelytoolongtobedisplayedononelineoftext" +
		//		"sotheprogramwillsplitmeupintomultiplelineslikeagoodprogramshould ");
		
		/*SongRecord sr = new SongRecord(0, new File("core/hi.txt"));
		String str = sr.getLyrics();
		long ti = System.nanoTime();
		boolean rv = containsCJKchars(str);
		long tf = System.nanoTime();
		System.out.println(rv + " (time: " + (tf-ti) + " ns)");*/
//	}
}
